package dou.net.socket;

import java.io.IOException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 实现了非阻塞套接字对象的功能
 * @author wizardc
 */
public abstract class AbstractClientSocket implements IClientSocket
{
    /**
     * 记录套接字服务对象
     */
    protected IServerSocket _server;

    /**
     * 记录套接字通道的 Selector 对象
     */
    protected Selector _selector;

    /**
     * 记录该套接字客户端的套接字通道对象
     */
    protected SocketChannel _socketChannel;

    /**
     * 记录协议包对象
     */
    protected IPacketBuffer _packetBuffer;

    /**
     * 记录需要发送的字节缓冲对象
     */
    protected Queue<ByteBuffer> _sendBuffers;

    /**
     * 记录该套接字通道的选择键对象
     */
    protected SelectionKey _readyKey;

    /**
     * 记录该套接字最后一次活跃的时间
     */
    private long _lastActiveTime;

    /**
     * 构造函数
     * @param server 套接字服务对象
     * @param selector 套接字通道的 Selector 对象
     * @param socketChannel 套接字客户端的套接字通道对象
     * @throws IOException 当出现 IO 异常时抛出该异常
     */
    public AbstractClientSocket(IServerSocket server, Selector selector, SocketChannel socketChannel) throws IOException
    {
        _server = server;
        _selector = selector;
        _socketChannel = socketChannel;
        // 创建协议包对象
        _packetBuffer = createPacketBuffer();
        // 创建发送的字节缓冲对象列表
        _sendBuffers = new LinkedList<ByteBuffer>();
        // 设置套接字通道为非阻塞模式
        _socketChannel.configureBlocking(false);
        // 注册该套接字通道的侦听类型为读写
        _readyKey = _socketChannel.register(_selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        // 记录该套接字最后一次活跃的时间
        _lastActiveTime = System.currentTimeMillis();
    }

    /**
     * 创建套接字对象用于解析数据的协议包对象
     * @return 对应的协议包对象
     */
    protected abstract IPacketBuffer createPacketBuffer();

    @Override
    public synchronized int readBuffer() throws IOException, InvalidProtocolException
    {
        // 协议包对象读取获取的字节流
        int length = _packetBuffer.readFrom(_socketChannel);
        // 是否为客户端关闭连接
        if(length == -1)
        {
            close();
        }
        else
        {
            // 如果该协议包接收完毕
            if(_packetBuffer.remaining() == 0)
            {
                packetArriveHandler(_packetBuffer);
                _packetBuffer = createPacketBuffer();
            }
            if(_readyKey != null && _readyKey.isValid())
            {
                // 添加 SelectionKey.OP_READ 标志位并唤醒 Selector 进程使其接着处理接收到数据的客户端套接字
                _readyKey.interestOps(_readyKey.interestOps() | SelectionKey.OP_READ);
                _selector.wakeup();
            }
        }
        // 记录该套接字最后一次活跃的时间
        _lastActiveTime = System.currentTimeMillis();
        return length;
    }

    /**
     * 每当客户端有数据到达时将会调用该方法
     * @param protocolPacket 客户端发送的字节缓冲对象
     * @throws InvalidProtocolException 当协议不正确时抛出该异常
     */
    protected abstract void packetArriveHandler(IPacketBuffer protocolPacket) throws InvalidProtocolException;

    @Override
    public synchronized void writeBuffer() throws IOException
    {
        if(_socketChannel == null)
        {
            throw new SocketException("Socket already closed!");
        }
        // 创建当前发送的字节缓冲对象的引用
        ByteBuffer byteBuffer = null;
        // 如果发送列表不为空
        while(!_sendBuffers.isEmpty())
        {
            if(byteBuffer == null || !byteBuffer.hasRemaining())
            {
                byteBuffer = _sendBuffers.peek();
            }
            if(byteBuffer == null)
            {
                continue;
            }
            // 发送数据并记录发送的字节
            int bytesLength = _socketChannel.write(byteBuffer);
            // 数据是否全部发送完毕
            if(!byteBuffer.hasRemaining())
            {
                // 数据发送完毕，从列表中去掉该对象
                _sendBuffers.poll();
            }
            // 数据还有留存，同时发送的字节数为 0 时
            else if(bytesLength == 0)
            {
                if(_readyKey != null && _readyKey.isValid())
                {
                    // 添加 SelectionKey.OP_WRITE 标志位并唤醒 Selector 进程发送后面添加的数据
                    _readyKey.interestOps(_readyKey.interestOps() | SelectionKey.OP_WRITE);
                    _selector.wakeup();
                }
                // 退出本次循环
                break;
            }
            // 记录该套接字最后一次活跃的时间
            _lastActiveTime = System.currentTimeMillis();
        }
    }

    /**
     * 向该客户端发送数据
     * @param byteBuffer 用于发送的字节缓冲对象
     */
    public synchronized void send(ByteBuffer byteBuffer)
    {
        _sendBuffers.add(byteBuffer);
        _server.send(this);
    }

    /**
     * 获取发送的数据是否还有剩余
     * @return 如果还有数据没有发送出去则返回 true，否则返回 false
     */
    public synchronized boolean hasRemaining()
    {
        return !_sendBuffers.isEmpty();
    }

    /**
     * 获取客户端 IP 地址字符串
     * @return 客户端 IP 地址字符串
     */
    public abstract String ipAddressStr();

    @Override
    public synchronized void close() throws IOException
    {
        // 取消选择键对象
        if(_readyKey != null && _readyKey.isValid())
        {
            _readyKey.cancel();
        }
        // 关闭客户端套接字对象
        if(_socketChannel != null && _socketChannel.isOpen())
        {
            _socketChannel.close();
            _socketChannel = null;
            closeHandler();
        }
        // 移除记录
        _server.remove(_socketChannel);
    }

    /**
     * 每当客户端套接字对象关闭后都会调用该方法
     */
    protected abstract void closeHandler();

    /**
     * 获取该套接字最后一次活跃的时间
     * @return 该套接字最后一次活跃的时间
     */
    public long getLastActiveTime()
    {
        return _lastActiveTime;
    }
}
